
package Principal;
import java.util.Hashtable;

enum RegSam {
    CB, CT, CP, PB, PT, SB, ST, HB, HT
    , LB, L1, L2, L3, L4, L5, L6 
}

enum Primitivas {
    id,not,and,or,inc,dec,neg,add,sub,mult,div,mod
    ,lt,le,gt,ge,eq,ne,to21,to22,newa,free,get,put,xor
}


public class Sam {

	//tabla de simbolos, para guardamos funciones ETIQ
	private Hashtable<String, Integer> tblsimb = new Hashtable<String, Integer>();
	
 
	
	//global nos determinara si es guardamos en Segmento Global
	boolean SegGlobal = true;
	


	public static final int MAX_MEM = 1024;
	public static final int NUM_PRIM = 24;
	

		
    public  int[] datos = new int[MAX_MEM];
    public  Instruccion[] codigo = new Instruccion[MAX_MEM];
	
    private int CB = 0;
    private int CT = 0;
    private int CP = 0;
    private int PT = MAX_MEM - 1;    
    private int PB = MAX_MEM - NUM_PRIM-1;	
    
    private int SB = 0;
    private int ST = 0;
    private int HB = MAX_MEM - 1;
    private int HT = MAX_MEM - 1;
    private int LB = 0;
    private int L1 = 0;
    private int L2 = 0;
    private int L3 = 0;
    private int L4 = 0;
    private int L5 = 0;
    private int L6 = 0;
    
 
    
     public Sam() {
    	 for (int i=0; i<MAX_MEM ; i++)
    		 datos[i] = -1;
     }
     
    
     // registrar un valor en el historico de una variable
	  public void putsimb(String name) {
	    int pos = CT;
	    
	    if (!tblsimb.containsKey(name)) {
	    	tblsimb.put(name, pos);
	    }
	    
	  }
	  
	  // recuperar un historico de valor de una variable
	  public int getsimbdir(String name)  {
	     int value = -1;		   
		    if (tblsimb.containsKey(name)) {
		      value = (int)tblsimb.get(name);		      		        
		    }	   		 
	   	return value;	  
	  }
	  public void printTblSmb(){
			System.out.println("Tabla Simbolos:" + tblsimb );	
			
	  }
	  
	  
	public int getCB() {
		return CB;
	}
	public void setCB(int cB) {
		CB = cB;
	}
	public int getCT() {
		return CT;
	}
	public void setCT(int cT) {
		CT = cT;
	}
	public int getCP() {
		return CP;
	}
	public void setCP(int cP) {
		CP = cP;
	}
	public int getPB() {
		return PB;
	}
	public void setPB(int pB) {
		PB = pB;
	}
	public int getPT() {
		return PT;
	}
	public void setPT(int pT) {
		PT = pT;
	}
	public int getSB() {
		return SB;
	}
	public void setSB(int sB) {
		SB = sB;
	}
	public int getST() {
		return ST;
	}
	public void setST(int sT) {
		ST = sT;
	}
	public int getHB() {
		return HB;
	}
	public void setHB(int hB) {
		HB = hB;
	}
	public int getHT() {
		return HT;
	}
	public void setHT(int hT) {
		HT = hT;
	}
	public int getLB() {
		return LB;
	}
	public void setLB(int lB) {
		LB = lB;
	}
	public int getL1() {
		return L1;
	}
	public void setL1(int l1) {
		L1 = l1;
	}
	public int getL2() {
		return L2;
	}
	public void setL2(int l2) {
		L2 = l2;
	}
	public int getL3() {
		return L3;
	}
	public void setL3(int l3) {
		L3 = l3;
	}
	public int getL4() {
		return L4;
	}
	public void setL4(int l4) {
		L4 = l4;
	}
	public int getL5() {
		return L5;
	}
	public void setL5(int l5) {
		L5 = l5;
	}
	public int getL6() {
		return L6;
	}
	public void setL6(int l6) {
		L6 = l6;
	}

	
	public int getRegistro(RegSam reg) {
		switch(reg) {
			case CB: return getCB(); 
			case CT: return getCT(); 
			case PB: return getPB(); 
			case PT: return getPT(); 
			case SB: return getSB(); 
			case ST: return getST(); 
			case HB: return getHB(); 
			case HT: return getHT(); 
			case LB: return getLB(); 
			case L1: return getL1(); 
			case L2: return getL2(); 
			case L3: return getL3(); 
			case L4: return getL4(); 
			case L5: return getL5(); 
			case L6: return getL6(); 
			case CP: return getCP(); 			
		}
		return 0;
	}	
	
	/* Creamos un metodo para cargar una instruccion en el segmento
	 * de codigo
	 */
	public void pushInstruccion(int op, String st, int r, int n, String d) {
	   	
		Instruccion aux = new Instruccion(op, r, n, d, st);
		codigo[CT] = aux;
		CT++;    //Incrementamos tope del codigo
				
	}
	
	public void mostrarEstado(){
		RegSam aux[] = RegSam.values();
		for (int i = 0; i < aux.length; i++) 
		   System.out.print(aux[i]+ " = " + getRegistro(aux[i]) + " ");
		System.out.println("");
	}
	public void printCode(){
		System.out.print("Codigo:(");
		for (int i=0; i <CT; i++){			
				System.out.print( i + "[" + codigo[i] + "], ");			
		}
		System.out.println(")");
	}
	
	public void mostrarMem(){
		System.out.print("Memoria:(");
		for (int i=0; i <15; i++){			
			System.out.print( i + "[" + datos[i] + "], ");			
		}
		System.out.println(")");
	}
	

	
	private void call_push(Instruccion ins) {
		
		//reservamos n palabras en la pila
		for (int i = 1;i <= Integer.valueOf(ins.getD());i++)
			if (SegGlobal) {
			   datos[ST] = 0; //Inicializamos a 0
			   ST++;
			   //LB = ST;
			}
			// No es global, tonces es registro de activacion
			else {				
				//LB++;
			}
	}
	
	private void call_pop(Instruccion ins) {
		
		//Liberamos n palabras en la pila
		for (int i = 1;i <= Integer.valueOf(ins.getD());i++)
			datos[ST-1] = -1; //Inicializamos a 0
			ST--;
			
	}
	
	private void call_load(Instruccion ins) {		
		
		//Si GLOBAL => SB => Ref Absoluta
		if (ins.getR() == 4) {
			for (int i =0;i < Integer.valueOf(ins.getN());i++) {
			   datos[ST] = datos[Integer.valueOf(ins.getD())+i];
			   ST++;
			}
		}
		else {
			for (int i = 0; i < Integer.valueOf(ins.getN()) ;i++) {		
			   datos[ST] = datos[LB+Integer.valueOf(ins.getD())+i]; //Inicializamos a 0			   
			   ST++;
			}				
		}
	}
	
	private void call_loada(Instruccion ins) {		
		
		//Si GLOBAL => SB => Ref Absoluta
		if (ins.getR() == 4) {			
			datos[ST] = Integer.valueOf(ins.getD());
			ST++;		
		}
		else {					
			datos[ST] = (LB+Integer.valueOf(ins.getD()));			   
			ST++;
							
		}
	}
	
	private void call_loadl(Instruccion ins) {		
		
		//guardamos la direccion en la posicion indicada		
			if (SegGlobal) {
			   
			   datos[ST] = Integer.valueOf(ins.getD());	
			   ST++;
			   //LB = ST;
			}
			// No es global, tonces es registro de activacion
			else {				
				//LB++;
			}
	}
	
	private void call_store(Instruccion ins) {
	
		//Si GLOBAL => SB => Ref Absoluta
		if (ins.getR() == 4) {
			for (int i = Integer.valueOf(ins.getN())-1;i >=0 ;i--) {		
			   datos[Integer.valueOf(ins.getD())+i] = datos[ST-1]; //Inicializamos a 0
			   datos[ST-1] = -1;
			   ST--;
			}
		}
		else {
			int despl = 0;
			// Si es un parametro => D negativo => desplazamos 2 menos quitando (ret y LB)
			if (Integer.valueOf(ins.getD()) < 0 ) despl = -2;
			
			for (int i = Integer.valueOf(ins.getN())-1;i >=0 ;i--) {		
			   datos[LB+Integer.valueOf(ins.getD())+i+despl] = datos[ST-1]; //Inicializamos a 0
			   datos[ST-1] = -1;
			   ST--;
			}				
		}
	}
	
	private void call_storei(Instruccion ins) {
		
		int pos = datos[ST-ins.getN()-1];  //Recuperamos la direccion en la pila
		
		for (int i = ins.getN()-1;i >=0 ;i--) {		
			datos[pos+i] = datos[ST-1]; //Inicializamos a 0		   
		    datos[ST-1] = -1;
		    ST--;
		}
		//Quitamos de la pila la direccion
		datos[ST-1] = -1;
	    ST--;

	}
	private void call_call(Instruccion ins) {
		
	
	    //Si es una f(x) definida => reg = SB(4)
		if (ins.getR() == 4) {
			//guardamos la direccion de retorno
			datos[ST] = CP+1;
			ST++;
			//guardamos la direccion LB anterior
			datos[ST] = LB;			
			ST++;
			LB = ST;  //LB Apunta a la primera variable local
			//buscamos la direccion del salto
			CP = getsimbdir(ins.getD())-1;
		}
		// Es una primitiva
		else {
			ejecutar_prim(ins);
		}
	}
	
	private void call_return(Instruccion ins) {
		
		//Liberamos variables locales
	    for (int i=ST; i >= LB;i--)
	    	datos[i] = -1;
	    //Quitamos de la pila registro activacion y parametros
	    ST = LB -3 - Integer.parseInt(ins.getD())+1;
		CP = datos[LB-2] -1 ;  //Quitamos 1 pq el swich lo incrementa
		LB = datos[LB-1];
		//Borramos parametros
		for (int i=ST; i <= ST+Integer.parseInt(ins.getD())+2;i++)
            datos[i] = -1;	
		
	}
	
	private void call_put(Instruccion ins) {
		
		int val;
		
		for (int i = ins.getR()-1;i >=0 ;i--) {					   
			val = datos[ST-1];
			System.out.println(val);
			datos[ST-1] = -1;
		    ST--;
		}
		
	}
	
	private void call_inc_dec(Instruccion ins,Primitivas op){
		
		int x1=0;
		int pos = datos[ST-1];  //Recuperamos la direccion en la pila
		
		int desp =ins.getR()-1;
		
		x1 = datos[pos];
		
		//ST = ST - desp - 1;  No desplazamos para almacenar el resultado
		switch (op) {
			case inc : x1++;break;
			case dec : x1--;break;
			case neg : x1 = -x1;break;
			case not : if ( x1 == 1) x1 = 0; else x1 = 0;break;
		}		
		for (int i=0; i<=desp ; i++) {
		   datos[ST] = x1;
		   ST++;
		}
		
	}
	
	private void call_ope_log(Instruccion ins,Primitivas op){
		
		int x1=0,x2=0;
		int rst = 0;
		
		int desp =ins.getR()-1;
		
		x2 = datos[ST-1-desp];		
		for (int i=0; i<=desp ; i++)
			datos[ST-1-desp+i] = -1;
		ST = ST - desp - 1;
		x1 = datos[ST-1-desp];
		//ST = ST - desp - 1;  No desplazamos para almacenar el resultado
		switch (op) {			
			case lt : if (x1 < x2 ) rst = 1;break;
			case le : if (x1 <= x2) rst = 1;break;
			case gt : if (x1 > x2 ) rst = 1;;break;
			case ge : if (x1 >= x2) rst = 1;;break;
			case eq : if (x1 == x2) rst = 1;break;
			case ne : if (x1 != x2) rst = 1;break;						
		}
		for (int i=0; i<=desp ; i++)
		   datos[ST-1-desp+i] = rst;
		
	}
	
	private void call_to22() {
		
		datos[ST] = datos[ST-1];
		ST++;
		
	}
	
	private void call_to21() {
		
		datos[ST-1] = -1;
		ST--;		
	}
	
	private void call_ope_arit(Instruccion ins,Primitivas op){
	
		int x1=0,x2=0;
		
		int desp =ins.getR()-1;
		
		x2 = datos[ST-1-desp];		
		for (int i=0; i<=desp ; i++)
			datos[ST-1-desp+i] = -1;
		ST = ST - desp - 1;
		x1 = datos[ST-1-desp];
		//ST = ST - desp - 1;  No desplazamos para almacenar el resultado
		switch (op) {
			case add : x1 += x2;break;
			case sub : x1 -= x2;break;
			case mult: x1 *= x2;break;
			case div : x1 /= x2;break;
			case mod : x1 %= x2;break;
			case and : x1 &= x2;break;
			case or  : x1 |= x2;break;
			case xor : x1 ^= x2;break;
			
		}
		for (int i=0; i<=desp ; i++)
		   datos[ST-1-desp+i] = x1;
		
	}
	
	private void ejecutar_prim(Instruccion ins) {
		
				
		switch (Primitivas.valueOf(ins.getD())) {
			case id  : break;
			case not : call_inc_dec(ins,Primitivas.not);break;
			case and : call_ope_arit(ins,Primitivas.and);break;
			case or  : call_ope_arit(ins,Primitivas.or);break;
			case inc : call_inc_dec(ins,Primitivas.inc);break;
			case dec : call_inc_dec(ins,Primitivas.dec);break;
			case neg : call_inc_dec(ins,Primitivas.neg);break;
			case add : call_ope_arit(ins,Primitivas.add);break;
			case sub : call_ope_arit(ins,Primitivas.sub);break;
			case mult: call_ope_arit(ins,Primitivas.mult);break;
			case div : call_ope_arit(ins,Primitivas.div);break;
			case mod : call_ope_arit(ins,Primitivas.mod);break;
			case lt  : call_ope_log(ins,Primitivas.lt);break;
			case le  : call_ope_log(ins,Primitivas.le);break;
			case gt  : call_ope_log(ins,Primitivas.gt);break;
			case ge  : call_ope_log(ins,Primitivas.ge);break;
			case eq  : call_ope_log(ins,Primitivas.eq);break;
			case ne  : call_ope_log(ins,Primitivas.ne);break;
			case to21: call_to21();break;
			case to22: call_to22();break;
			case newa: break;
			case free: break;
			case get : break;
			case put : call_put(ins);break;
			case xor : call_ope_arit(ins,Primitivas.xor);break;

			default : 			
			  int pos = datos[ST-1];
			  System.out.println(pos);
		}
		
	}
	public boolean ejecuta(Instruccion ins) {
		
		boolean salir = false;
		
		switch(ins.getOp()) {
			case 0: call_load(ins);break;
			case 1: call_loada(ins);break;
			case 3: call_loadl(ins);break;
			case 4: call_store(ins);break;
			case 5: call_storei(ins);break;
			case 6: call_call(ins);break;
			//Push
			case 9: call_push(ins);break;
			case 10: call_pop(ins);break;
			case 8: call_return(ins);break;
			case 14: salir = true;break;
		   
		}
		CP++;
		return salir;
	}
	//Ejecutamos las instrucciones cargadas en la pila de codigo.
	public void ejecutar_codigo() {
		boolean salir = false;
		Instruccion aux;
		mostrarEstado();
		while (!salir) {
		    aux = codigo[CP];
		    System.out.println(codigo[CP]);
		    System.out.println(" ");
		    salir= ejecuta(aux);
		    mostrarEstado();	
		    mostrarMem();
		    printTblSmb();
			//salir = true;
		}
	}
	
	}
